package com.spring.security.filter;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.spring.security.service.JwtService;
import com.spring.security.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author HouKunLin
 */
@Slf4j
@Component
public class JWTOncePerRequestFilter extends OncePerRequestFilter {

    @Autowired
    private JwtService jwtService;

    @Autowired
    private UserService userService;

    /**
     * Details 构建器
     *
     * @see AbstractAuthenticationProcessingFilter#authenticationDetailsSource
     * @see UsernamePasswordAuthenticationFilter#setDetails(javax.servlet.http.HttpServletRequest, org.springframework.security.authentication.UsernamePasswordAuthenticationToken)
     */
    private WebAuthenticationDetailsSource webAuthenticationDetailsSource = new WebAuthenticationDetailsSource();

    /**
     * @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#authoritiesMapper
     * @see AbstractUserDetailsAuthenticationProvider#createSuccessAuthentication(java.lang.Object, org.springframework.security.core.Authentication, org.springframework.security.core.userdetails.UserDetails)
     */
    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Authentication principal = SecurityContextHolder.getContext().getAuthentication();
        log.debug("Authentication : {}", principal);
        if (principal != null) {
            log.debug("Authentication Class : {}", principal.getClass().getName());
            filterChain.doFilter(request, response);
            return;
        }

        String token = getAuthorization(request);
        if (token != null) {
            try {
                // 解密 Token
                DecodedJWT jwt = jwtService.decodedJWT(token);
                String username = jwt.getClaim("username").asString();
                UserDetails user = userService.loadUserByUsername(username);

                log.debug("JWT id : {}", jwt.getClaim("id").asInt());
                log.debug("JWT username : {}", jwt.getClaim("username").asString());
                log.debug("JWT nick : {}", jwt.getClaim("nick").asString());

                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, user
                        .getPassword(), authoritiesMapper.mapAuthorities(user.getAuthorities()));
                setDetails(request, authenticationToken);
                // @see ProviderManager#eraseCredentialsAfterAuthentication
                authenticationToken.eraseCredentials();
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            } catch (Exception e) {
                log.debug("用户 Token 数据验证失败，这是一个不可信的 Token", e);
            }
        }
        filterChain.doFilter(request, response);
    }

    /**
     * 获得请求头的 Authorization 信息
     *
     * @param request 请求
     * @return Bearer 信息
     */
    private String getAuthorization(HttpServletRequest request) {
        String authorization = request.getHeader("Authorization");
        log.debug("Http Header Authorization : {}", authorization);
        if (authorization == null) {
            return null;
        }
        // "Bearer "
        int bearerLen = 7;
        if (authorization.length() <= bearerLen) {
            return null;
        }

        String bearer = authorization.substring(bearerLen).trim();
        if (bearer.isEmpty()) {
            bearer = null;
        }

        log.debug("Bearer Token : {}", bearer);

        return bearer;
    }


    /**
     * 设置 Details 信息
     *
     * @param request     当前请求
     * @param authRequest 认证信息
     * @see UsernamePasswordAuthenticationFilter#setDetails(javax.servlet.http.HttpServletRequest, org.springframework.security.authentication.UsernamePasswordAuthenticationToken)
     */
    private void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(webAuthenticationDetailsSource.buildDetails(request));
    }
}
